package co.com.smart.hawk.utilities;

import java.awt.event.ComponentListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.lang.reflect.Method;
import java.util.*;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

/**
 * Class manager tables SmartHawk
 *
 * @param <C> 
 * @author <h1>Andres Felipe Escobar Lopez - andresfescobar92@gmail.com</h1>
 */
public class HawkTableMgr<C> {

    private JTable table;
    private List<Object> newsObjects = new ArrayList<>();
    private PositionObject position;
    private List<C> data;

    public enum PositionObject {

        FIRST,
        LAST
    }

    /**
     * Component for using an object tables starting
     *
     * @param table
     * @throws Throwable  
     */
    public HawkTableMgr(JTable table) throws Throwable {
        this.table = table;
        this.newsObjects = new ArrayList<>();
        this.table.setDefaultRenderer(Object.class, new HawkCellRedererMgr());
        this.table.setDefaultEditor(Object.class, new HawkCellEditorMgr());
        try {
            addColumnObjectRow();
        } catch (Throwable thr) {
            throw new Throwable("La columna 'ObjectRow' no existe en el modelo de la tabla", thr);
        }
    }

    /**
     * add in the table row with the object information
     *
     * @param data
     * @throws Throwable  
     */
    public void addRowObject(C data) throws Throwable {

        if (data != null) {
            if (this.data == null) {
                this.data = new ArrayList<>();
            }
            this.data.add(data);
            List<C> dataObjectUpdate = new ArrayList<>(this.data);
            setData(dataObjectUpdate);
        }

    }

    /**
     * Fill in the table with the list object information
     *
     * @param data
     * @throws Throwable  
     */
    public void setData(List<C> data) throws Throwable {

        if (data == null) {
            clearAll();
            return;
        }

        if (data.isEmpty()) {
            clearAll();
            return;
        }

        if (this.data == null) {
            this.data = new ArrayList<>();
        }

        if (this.table.getRowCount() != 0) {
            clearAll();
        }

        for (Iterator<C> it = data.iterator(); it.hasNext();) {
            C object = it.next();
            Class classInvoke = object.getClass();
            Method method = null;
            try {
                method = classInvoke.getMethod("showDataTable");
            } catch (NoSuchMethodException ex) {
                ex.printStackTrace(System.err);
                throw new Throwable("No existe el metodo getShowDataTable() en el objeto -> " + classInvoke.getName());
            }
            this.data.add(object);
            Object result = method.invoke(object, new Object[0]);
            Object[] dataTable = joinigObjects((Object[]) result, object);
            System.out.println(dataTable[0] + "-" + dataTable[1] + "-" + dataTable[2]);
            getTableModel().addRow(dataTable);
            repaint();
        }
    }

    /**
     * Create column object table record
     */
    private void addColumnObjectRow() throws Throwable {
        List<Object> columnDefined = new ArrayList<>();
        columnDefined.add("ObjectRow");
        Enumeration<TableColumn> enumeration = this.table.getColumnModel().getColumns();
        while (enumeration.hasMoreElements()) {
            TableColumn column = enumeration.nextElement();
            columnDefined.add(column.getIdentifier());
        }
        this.table.setModel(new DefaultTableModel(new Object[][]{}, columnDefined.toArray()));
        try {
            this.table.getColumn("ObjectRow").setMinWidth(0);
            this.table.getColumn("ObjectRow").setMaxWidth(0);
            this.table.getColumn("ObjectRow").setPreferredWidth(0);
            this.table.getColumn("ObjectRow").setWidth(0);
        } catch (Throwable thr) {
            throw new Throwable("La columna 'ObjectRow' no existe en el modelo de la tabla", thr);
        }
        repaint();
    }

    /**
     * Gets the table model
     *
     * @return
     * @throws Throwable
     */
    public DefaultTableModel getTableModel() throws Throwable {
        if (this.table == null) {
            throw new Throwable(new NullPointerException("La tabla enviada no está inicializada"));
        }
        DefaultTableModel dtm = (DefaultTableModel) this.table.getModel();
        return dtm;
    }

    /**
     * New objects to arrangements defined in the POJO table Unite
     *
     * @param data
     * @return
     */
    private Object[] joinigObjects(Object[] data, Object objectTable) {
        List<Object> newObject = new ArrayList();
        newObject.add(objectTable);
        if (this.newsObjects != null && this.newsObjects.size() > 0) {

            switch (this.position) {
                case FIRST:
                    for (Object object : newsObjects) {
                        newObject.add(object);
                    }
                    newObject.addAll(Arrays.asList(data));

                    break;
                case LAST:

                    newObject.addAll(Arrays.asList(data));

                    for (Object object : newsObjects) {
                        newObject.add(object);
                    }
                    break;
                default:
                    newObject.addAll(Arrays.asList(data));

                    for (Object object : newsObjects) {
                        newObject.add(object);
                    }
                    break;
            }
            return newObject.toArray();
        } else {
            newObject.addAll(Arrays.asList(data));
            return newObject.toArray();
        }
    }

    /**
     * Return value object from column request. <h1>Important :</h1>Throws
     * exception when there is no selected column and row
     *
     * @param column
     * @return 
     * @throws Throwable Throws exception when there column no exists
     */
    public Object getValue(String column) throws Throwable {
        Object value = null;
        try {
            this.table.getColumn(column);
            value = getTableModel().getValueAt(this.table.getSelectedRow(), getTableModel().findColumn(column));
        } catch (Throwable thr) {
            throw new Throwable("La columna '" + column + "' no existe en el modelo de la tabla", thr);
        }
        return value;
    }

    /**
     * Return value object from column request. <h1>Important :</h1>Throws
     * exception when there columns no exists
     *
     * @param row
     * @param column
     * @return 
     * @throws Throwable Throws exception when there column no exists
     */
    public Object getValue(int row, String column) throws Throwable {
        Object value = null;
        try {
            this.table.getColumn(column);
            value = getTableModel().getValueAt(row, getTableModel().findColumn(column));
        } catch (Throwable thr) {
            throw new Throwable("La columna '" + column + "' no existe en el modelo de la tabla", thr);
        }
        return value;
    }

    /**
     * Gets the selected record
     *
     * @return
     * @throws Throwable
     */
    public C getSelectObject() throws Throwable {
        C object = null;

        try {
            this.table.getColumn("ObjectRow");
        } catch (Throwable thr) {
            throw new Throwable("La columna 'ObjectRow' no existe en el modelo de la tabla", thr);
        }
        try {
            object = (C) getTableModel().getValueAt(this.table.getSelectedRow(), 0);
        } catch (Throwable thr) {
            throw new Throwable("No es  posible castear el objeto de la tabla al tipo de tabla enviada", thr);
        }

        return object;
    }

    /**
     * Returns the table inserted in the list
     *
     * @return
     */
    public List<C> getData() {
        return this.data;
    }

    /**
     * Adds new objects to the table
     *
     * @param object
     */
    public void addNewValueColumn(Object object) {
        this.newsObjects.add(object);
    }

    /**
     * Return table instance
     * @return 
     */
    public JTable getTable() {
        return this.table;
    }

    /**
     * add events into table instance
     * @param event
     * @throws Throwable 
     */
    public void addTableEvents(Object event) throws Throwable {
        if (getTable() == null) {
            throw new NullPointerException("La tabla enviada no está inicializada");
        }
        if (event instanceof MouseListener) {
            getTable().addMouseListener((MouseListener) event);
        }
        if (event instanceof KeyListener) {
            getTable().addKeyListener((KeyListener) event);
        }
        if (event instanceof ComponentListener) {
            getTable().addComponentListener((ComponentListener) event);
        }
        if (event instanceof FocusListener) {
            getTable().addFocusListener((FocusListener)event);
        }
    }

    /**
     * Defines the order of the new objects to fill in the table
     *
     * @param position
     */
    public void setPosition(PositionObject position) {
        this.position = position;
    }

    /**
     * Clean the table and refreshes
     */
    private void clear() throws Throwable {
        refreshTableRecursive();
        this.table.removeAll();
        this.repaint();
    }

    /**
     * Clean all components
     */
    public void clearAll() {
        if (this.data != null) {
            this.data.clear();
        }
        try {
            clear();
        } catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
    }

    /**
     * Clean Recursive all components 
     */
    private void refreshTableRecursive() throws Throwable {
        if (this.table != null) {
            if (getTableModel().getRowCount() != 0) {
                int row = getTableModel().getRowCount();
                for (int i = 0; row > i; i++) {
                    getTableModel().removeRow(0);
                }
            }
        }
    }

    /**
     * Refreshes the table
     */
    public void repaint() {
        this.table.repaint();
    }
}
